package com.cmc.cloud.cmclink.doc.util.sepcial;

import com.cmc.cloud.cmclink.doc.constants.errorconstant.BlnoBlPrintLogErrorCodeConstants;
import com.cmc.cloud.cmclink.doc.enums.PrintStatusEnum;
import com.cmc.cloud.cmclink.doc.vo.blnoblprintlogvo.BlnoMaintenanceInfoRespVO;
import com.cmc.cloud.cmclink.doc.vo.docbkconfirmprintsetupvo.DocBkConfirmPrintSetupRespVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.socargovo.SoCargoRespVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.sovo.SoDetailRespVO;
import com.cmc.cloud.cmclink.doc.vo.sovo.sovo.SoRespVO;
import com.cmes.framework.common.exception.util.ServiceExceptionUtil;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ObjectUtils;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

/**
 * PDF生成工具类
 *
 * @author LYP
 * @date 2025/08/01 11:25
 */
@Slf4j
public class PdfUtil {
    // 静态常量配置
    /**
     * PDF模板文件的基础路径（存放HTML模板文件的目录）
     */
    public static final String TEMPLATE_PREFIX = "pdf/templates/";
    /**
     * PDF模板文件的默认后缀（使用HTML格式作为模板源文件）
     */
    public static final String TEMPLATE_SUFFIX = ".html";
    /**
     * PDF生成时使用的默认字体文件路径（存放字体资源的目录）
     */
    public static final String DEFAULT_FONT_PATH = "pdf/fonts/";
    /**
     * PDF中嵌入的图片资源路径（存放图片素材的目录）
     */
    public static final String IMAGE_PATH = "pdf/img/";
    private static final DateTimeFormatter FORMATTER_AMEND = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    /**
     * 生成PDF并写入响应流
     *
     * @param response HTTP响应
     * @param params   模板参数
     */
    public void buildPdf(HttpServletResponse response, Map<String, Object> params) {
        // 提取参数
        String pdfFileName = (String) params.get("pdfFileName");
        String templateName = (String) params.get("templateName");
        Map<String, Object> variables = (Map<String, Object>) params.get("variables");
        // 设置文件名，并指定编码格式
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/pdf;charset=UTF-8");
        response.setHeader("Content-Disposition", "attachment; filename=" + encodeFileName(pdfFileName + ".pdf"));
        try (OutputStream os = response.getOutputStream()) {
            // 渲染模板
            String htmlContent = renderTemplate(templateName, pdfFileName, variables);
            // 生成PDF
            generatePdf(htmlContent, os);
        } catch (Exception e) {
            throw new RuntimeException("生成PDF失败", e);
        }
    }

    /**
     * 生成PDF并返回文件流
     *
     * @param params 模板参数
     * @return PDF文件流
     */
    public ByteArrayOutputStream generatePdfStream(Map<String, Object> params) {
        // 提取参数
        String templateName = (String) params.get("templateName");
        String pdfFileName = (String) params.get("pdfFileName");
        Map<String, Object> variables = (Map<String, Object>) params.get("variables");

        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            // 渲染模板
            String htmlContent = renderTemplate(templateName, pdfFileName, variables);

            // 生成PDF并返回
            generatePdf(htmlContent, baos);

            return baos;
        } catch (Exception e) {
            throw ServiceExceptionUtil.exception(BlnoBlPrintLogErrorCodeConstants.BLNO_PDF_TEMPLATE_ERROR);
        }
    }

    /**
     * 渲染Thymeleaf模板
     */
    private static String renderTemplate(String templateName, String pdfFileName, Map<String, Object> variables) {
        log.info("打印模板：{}，打印文件名称：{}，打印入参：{}",templateName,pdfFileName,variables);
        // 构造模板引擎
        ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
        resolver.setPrefix(TEMPLATE_PREFIX);
        resolver.setSuffix(TEMPLATE_SUFFIX);

        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(resolver);
        // 构造上下文
        Context context = new Context();
        context.setVariable("templateName", templateName);
        context.setVariable("pdfFileName", pdfFileName);
        context.setVariables(variables);
        String htmlContent = templateEngine.process(templateName, context);
        if (!ObjectUtils.isEmpty(htmlContent)){
            log.info("htlmContent length {}",htmlContent.length());
            log.debug("htlmContent: {}",htmlContent);
        }
        // 渲染模板
        return htmlContent;
    }

    public static void setFornts(PdfRendererBuilder builder) {
        String[] fonts = new String[]{"angsau", "arial", "simsun", "verdana"};
        for (String font : fonts) {
            ClassPathResource classPathResource = new ClassPathResource(DEFAULT_FONT_PATH + font + ".ttf");
            try {
                InputStream inputStream = classPathResource.getInputStream();
                builder.useFont(() -> {
                    try {
                        return inputStream;
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }, font);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 生成PDF
     */
    private static void generatePdf(String htmlContent, OutputStream os) throws Exception {
        PdfRendererBuilder builder = new PdfRendererBuilder();
        // 使用ClassLoader加载字体文件，适应JAR包环境
        // 注册所有字体
        setFornts(builder);
        builder.useFastMode();
        try {
            // 使用classpath路径加载图片资源
            ClassPathResource classPathResource = new ClassPathResource(IMAGE_PATH);
            builder.withHtmlContent(htmlContent, classPathResource.getPath());
        } catch (Exception e) {
            throw new RuntimeException("加载映像资源失败: " + IMAGE_PATH, e);
        }
        builder.toStream(os);
        builder.run();
    }

    /**
     * 编码文件名
     */
    private static String encodeFileName(String fileName) {
        try {
            return java.net.URLEncoder.encode(fileName, "UTF-8");
        } catch (Exception e) {
            return fileName;
        }
    }

  
    /**
     * 提供静态方法供外部调用
     *
     * @param response
     * @param params
     */
    public void buildPdfStatic(HttpServletResponse response, Map<String, Object> params) {
        buildPdf(response, params);
    }

    public void buildPdfStatic(HttpServletResponse response, SoDetailRespVO soDetail, Map<String, String> codeMap) {
        //TOKYO (JPTYO) 英文名（港口代码） 在航线基础数据中查询
        Map<String, Object> variables = new HashMap<>(16);
        SoRespVO so = soDetail.getSo();
        variables.put("bookingNo", so.getBookingNo());
        variables.put("dateOfIssue", LocalDate.now().toString());

        variables.put("carrierCode", so.getSpaceOwner());
        variables.put("refNo", so.getReferenceNo());
        variables.put("contractNo", so.getAgreementNo());
        variables.put("bookingStatus", "Booking Confirmed");
        variables.put("serviceMode", so.getShippingTerm());

        variables.put("bookingParty", so.getBookingParty());
        variables.put("shipper", soDetail.getSoScn() != null ? soDetail.getSoScn().getCompanyName() : "");
        variables.put("forwarder", so.getTopAgent());
        variables.put("portOfReceipt", codeMap.get(so.getPorCode()));
        variables.put("portOfLoading", codeMap.get(so.getPolCode()));
        variables.put("vesselVoy", so.getBookingVessel() + " " + so.getBookingVoyage());
        variables.put("portOfDelivery", codeMap.get(so.getDelCode()));
        variables.put("etd", so.getPolEtd());
        variables.put("eta", so.getPodEta());
        SoCargoRespVO cargo = soDetail.getSoCargoList().get(0);
        StringBuilder containerQtyTypes = new StringBuilder();
        soDetail.getSoForecastList().forEach(e -> {
            containerQtyTypes.append(e.getCntrQuantity() + " * " + e.getCntrSize() + e.getCntrType() + " G.W. " + e.getPerCntrWgt().toPlainString() + "KGS ");
        });
        variables.put("containerQtyTypes", containerQtyTypes.toString());
        variables.put("commodityName", cargo.getCommodityCn() + " " + cargo.getCommodityEn() + " " + cargo.getHsCode());
        // 特种货查询
        variables.put("unno", cargo.getUnNo() + cargo.getImoClass());
        variables.put("temp", cargo.getTemperature() + cargo.getVantilation());
        variables.put("grossWeight", cargo.getGrossWeight());
        //通过booking航线查询时间
        Map<String, String> timeMap = soDetail.getTimeMap();
        if (timeMap != null) {
            variables.put("emptyPickupStart", timeMap.get("empty_pickup_start"));
            variables.put("cyOpen", timeMap.get("cy_open"));
            variables.put("cyCutoff", timeMap.get("cy_cut_off"));
            variables.put("siCutoff", timeMap.get("si_cut_off"));
            variables.put("vgmCutoff", timeMap.get("vgm_cut_off"));
        }
        variables.put("firstTsPort", codeMap.get(so.getPolCode()));
        variables.put("portOfDischarge", codeMap.get(so.getPodCode()));
        variables.put("secondTsPort", codeMap.get(so.getSecondTsPort()));
        //第二个liner
        variables.put("pickupLocation", "提箱堆场地址，联系方式，堆场代码等");
        variables.put("returnCyLocation", "还箱堆场，联系方式，码头代码等等");
        variables.put("title", "SINOLINES BOOKING CONFIRMATION");
        // porCode 查找模板，可以为空
        DocBkConfirmPrintSetupRespVO confirmPrintSetup = soDetail.getConfirmPrintSetup();
        if (confirmPrintSetup != null) {
            variables.put("companyName", confirmPrintSetup.getCompanyName());
            variables.put("contactInfo", confirmPrintSetup.getTelephone() + " " + confirmPrintSetup.getEmail());
            variables.put("portSpecificRemarks", confirmPrintSetup.getRemark());
        }
        Map<String, Object> params = new HashMap<>(16);
        String fileName = so.getBookingNo() + "BC" + LocalDateTime.now().format(FORMATTER_AMEND);
        params.put("pdfFileName", fileName);
        params.put("templateName", "booking_confirmation");
        params.put("variables", variables);
        buildPdfStatic(response, params);
    }


    public ByteArrayOutputStream buildPdfStaticAddress(SoDetailRespVO soDetail, Map<String, String> codeMap) {
        //TOKYO (JPTYO) 英文名（港口代码） 在航线基础数据中查询
        Map<String, Object> variables = new HashMap<>(16);
        SoRespVO so = soDetail.getSo();
        variables.put("bookingNo", so.getBookingNo());
        variables.put("dateOfIssue", LocalDate.now().toString());

        variables.put("carrierCode", so.getSpaceOwner());
        variables.put("refNo", so.getReferenceNo());
        variables.put("contractNo", so.getAgreementNo());
        variables.put("bookingStatus", "Booking Confirmed");
        variables.put("serviceMode", so.getShippingTerm());

        variables.put("bookingParty", so.getBookingParty());
        variables.put("shipper", soDetail.getSoScn() != null ? soDetail.getSoScn().getCompanyName() : "");
        variables.put("forwarder", so.getTopAgent());
        variables.put("portOfReceipt", codeMap.get(so.getPorCode()));
        variables.put("portOfLoading", codeMap.get(so.getPolCode()));
        variables.put("vesselVoy", so.getBookingVessel() + " " + so.getBookingVoyage());
        variables.put("portOfDelivery", codeMap.get(so.getDelCode()));
        variables.put("etd", so.getPolEtd());
        variables.put("eta", so.getPodEta());
        SoCargoRespVO cargo = soDetail.getSoCargoList().get(0);
        StringBuilder containerQtyTypes = new StringBuilder();
        soDetail.getSoForecastList().forEach(e -> {
            containerQtyTypes.append(e.getCntrQuantity()).append(" * ").append(e.getCntrSize()).append(e.getCntrType()).append(" G.W. ").append(e.getPerCntrWgt().toPlainString()).append("KGS ");
        });
        variables.put("containerQtyTypes", containerQtyTypes.toString());
        variables.put("commodityName", cargo.getCommodityCn() + " " + cargo.getCommodityEn() + " " + cargo.getHsCode());
        // 特种货查询
        variables.put("unno", cargo.getUnNo() + cargo.getImoClass());
        variables.put("temp", cargo.getTemperature() + cargo.getVantilation());
        variables.put("grossWeight", cargo.getGrossWeight());
        //通过booking航线查询时间
        Map<String, String> timeMap = soDetail.getTimeMap();
        if (timeMap != null) {
            variables.put("emptyPickupStart", timeMap.get("empty_pickup_start"));
            variables.put("cyOpen", timeMap.get("cy_open"));
            variables.put("cyCutoff", timeMap.get("cy_cut_off"));
            variables.put("siCutoff", timeMap.get("si_cut_off"));
            variables.put("vgmCutoff", timeMap.get("vgm_cut_off"));
        }
        variables.put("firstTsPort", codeMap.get(so.getPolCode()));
        variables.put("portOfDischarge", codeMap.get(so.getPodCode()));
        variables.put("secondTsPort", codeMap.get(so.getSecondTsPort()));
        //第二个liner
        variables.put("pickupLocation", "提箱堆场地址，联系方式，堆场代码等");
        variables.put("returnCyLocation", "还箱堆场，联系方式，码头代码等等");
        variables.put("title", "SINOLINES BOOKING CONFIRMATION");
        // porCode 查找模板，可以为空
        DocBkConfirmPrintSetupRespVO confirmPrintSetup = soDetail.getConfirmPrintSetup();
        if (confirmPrintSetup != null) {
            variables.put("companyName", confirmPrintSetup.getCompanyName());
            variables.put("contactInfo", confirmPrintSetup.getTelephone() + " " + confirmPrintSetup.getEmail());
            variables.put("portSpecificRemarks", confirmPrintSetup.getRemark());
        }
        Map<String, Object> params = new HashMap<>(16);
        params.put("pdfFileName", codeMap.get("fileName"));
        params.put("templateName", "booking_confirmation");
        params.put("variables", variables);
        return generatePdfStream(params);
    }

    public ByteArrayOutputStream generateSeaWaybillPdf(String documentObjectives, BlnoMaintenanceInfoRespVO blnoMaintenance) {
        Map<String, Object> variables = new HashMap<>(16);

        //特殊信息
        if (blnoMaintenance.getBookingNo() != null) {
            if (PrintStatusEnum.W.getValue().equals(blnoMaintenance.getSignType())) {
                variables.put("bookingRef", blnoMaintenance.getBookingNo());
                variables.put("bookingNo", blnoMaintenance.getBookingNo());
                variables.put("shippersRef", blnoMaintenance.getBookingNo());
            }
        }

        variables.put("blNo", blnoMaintenance.getBlNo());

        // 通用信息
        variables.put("shipper1", blnoMaintenance.getShipper());

        variables.put("consignee1", blnoMaintenance.getConsignee());

        variables.put("notifyParty1", blnoMaintenance.getNotifyParty());

        variables.put("pointAndCountryOfOrigin", "");

        variables.put("preCarriageByk", "");
        variables.put("placeOfReceipt", blnoMaintenance.getPortOfLoading());

        variables.put("vesselVoyageNo", blnoMaintenance.getVesselVoyageNo());
        variables.put("portOfLoading", blnoMaintenance.getPortOfLoading());

        variables.put("portOfDischarge", blnoMaintenance.getPortOfDischarge());
        variables.put("placeOfDelivery", blnoMaintenance.getPlaceOfDelivery());

        variables.put("pointAndCountrOfOrigin", "");
        variables.put("porwardingAgentReferences", "");
        variables.put("serviceContractNo", "");
        variables.put("documentNo", "");
        variables.put("EPortReferences", "");


        variables.put("marksNos1", blnoMaintenance.getMarks());

        variables.put("noOfPackages", blnoMaintenance.getPackagesContainers());

        variables.put("descriptionOfGoods1", blnoMaintenance.getDescriptionOfGoods());

        variables.put("grossWeight", blnoMaintenance.getGrossWeight());

        variables.put("measurement", blnoMaintenance.getMeasurement());

        variables.put("temperatureVentilation", blnoMaintenance.getTemperatureVentilation());
        variables.put("dangerousGoods", blnoMaintenance.getDangerousGoods());
        variables.put("lclBlNos", blnoMaintenance.getLclBlNos());

        variables.put("totalNumber", blnoMaintenance.getTotalPackagesInWords());
//        13
        variables.put("freightAndCharges", blnoMaintenance.getFreightCharges());
//        19
        variables.put("original", blnoMaintenance.getOriginalBlNumber());

        variables.put("regardingTranshipment1", blnoMaintenance.getTranshipmentContact());

        variables.put("placeDateIssue1", blnoMaintenance.getPlaceDateOfIssue());

        variables.put("prepaid", blnoMaintenance.getPrepaidCollect());
        variables.put("prepaidA", blnoMaintenance.getPrepaidAt());
        variables.put("payabl", blnoMaintenance.getPayableAt());

        variables.put("totalPrepaid", blnoMaintenance.getTotalPrepaid());
        variables.put("ladenOnBoard", blnoMaintenance.getLadenOnBoard());

        variables.put("signedForThe1", blnoMaintenance.getSignedForCarrier());

        variables.put("fontSize", "10");
        // 字体大小
        Map<String, Object> params = new HashMap<>(16);
        params.put("pdfFileName", blnoMaintenance.getFileName());
        //导出文件名
        params.put("templateName", documentObjectives);
        //指定使用的模板名称
        params.put("variables", variables);
        //模板渲染数据
        return generatePdfStream(params);
    }

    public void printTheDraft(HttpServletResponse response, BlnoMaintenanceInfoRespVO blnoMaintenance) {
        Map<String, Object> variables = new HashMap<>(16);

        //特殊信息
        if (blnoMaintenance.getBookingNo() != null) {
            if (PrintStatusEnum.W.getValue().equals(blnoMaintenance.getSignType())) {
                variables.put("bookingRef", blnoMaintenance.getBookingNo());
                variables.put("wblNo", blnoMaintenance.getBookingNo());
                variables.put("shippersRef", blnoMaintenance.getBookingNo());
            }
        }

        variables.put("blNo", blnoMaintenance.getBlNo());

        // 通用信息
        variables.put("shipper1", blnoMaintenance.getShipper());

        variables.put("consignee1", blnoMaintenance.getConsignee());

        variables.put("notifyParty1", blnoMaintenance.getNotifyParty());

        variables.put("pointAndCountryOfOrigin", "");

        variables.put("preCarriageByk", "");
        variables.put("placeOfReceipt", blnoMaintenance.getPortOfLoading());

        variables.put("vesselVoyageNo", blnoMaintenance.getVesselVoyageNo());
        variables.put("portOfLoading", blnoMaintenance.getPortOfLoading());

        variables.put("portOfDischarge", blnoMaintenance.getPortOfDischarge());
        variables.put("placeOfDelivery", blnoMaintenance.getPlaceOfDelivery());

        variables.put("pointAndCountrOfOrigin", "");
        variables.put("porwardingAgentReferences", "");
        variables.put("serviceContractNo", "");
        variables.put("documentNo", "");
        variables.put("EPortReferences", "");

        variables.put("marksNos1", blnoMaintenance.getMarks());

        variables.put("noOfPackages", blnoMaintenance.getPackagesContainers());

        variables.put("descriptionOfGoods1", blnoMaintenance.getDescriptionOfGoods());

        variables.put("grossWeight", blnoMaintenance.getGrossWeight());

        variables.put("measurement", blnoMaintenance.getMeasurement());

        variables.put("temperatureVentilation", blnoMaintenance.getTemperatureVentilation());
        variables.put("dangerousGoods", blnoMaintenance.getDangerousGoods());
        variables.put("lclBlNos", blnoMaintenance.getLclBlNos());

        variables.put("totalNumber", blnoMaintenance.getTotalPackagesInWords());
//        13
        variables.put("freightAndCharges", blnoMaintenance.getFreightCharges());

//        19
        variables.put("original", blnoMaintenance.getOriginalBlNumber());

        variables.put("regardingTranshipment1", blnoMaintenance.getTranshipmentContact());

        variables.put("placeDateIssue1", blnoMaintenance.getPlaceDateOfIssue());

        variables.put("prepaid", blnoMaintenance.getPrepaidCollect());
        variables.put("prepaidA", blnoMaintenance.getPrepaidAt());
        variables.put("payabl", blnoMaintenance.getPayableAt());

        variables.put("totalPrepaid", blnoMaintenance.getTotalPrepaid());
        variables.put("ladenOnBoard", blnoMaintenance.getLadenOnBoard());

        variables.put("signedForThe1", blnoMaintenance.getSignedForCarrier());

        variables.put("fontSize", "10");
        // 字体大小
        Map<String, Object> params = new HashMap<>(16);
        params.put("pdfFileName", blnoMaintenance.getFileName());
        //导出文件名
        params.put("templateName", "DraftBL");
        //指定使用的模板名称
        params.put("variables", variables);
        //模板渲染数据
        buildPdfStatic(response, params);
    }
}
